﻿using AzureVMDashboard.UI;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Management.Compute;
using Microsoft.WindowsAzure.Management.Compute.Models;
//using Microsoft.WindowsAzure.Management.Model;
//using Microsoft.WindowsAzure.Management.ServiceManagement.Model;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Xml;
using System.Linq;

namespace AzureVMDashboard.Model
{
    /// <remarks>Singleton</remarks>
    public class AzureVMController : ModelBase
    {
        private static volatile AzureVMController instance;
        private static object syncRoot = new Object();

        private DirectoryInfo WorkingFolder;

        private AzureVMController(DirectoryInfo workingFolder)
        {
            ControllerInitialized = false;
            IsWorking = false;
            AzureSubscriptions = new ObservableCollection<AzureSubscription>();


            if (workingFolder.Exists)
                WorkingFolder = workingFolder;
            else
                Logger.LogEntry(LogType.Warning, "Working folder not exists: " + workingFolder.FullName);
        }

        public static AzureVMController GetInstance(DirectoryInfo workingDir)
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    if (instance == null)
                        instance = new AzureVMController(workingDir);
                }
            }

            return instance;
        }


        #region Properties
        /// <summary>
        /// File generated from Get-AzurePublishSettingsFile, contains a management certificate and all subscriptions
        /// </summary>   
        public FileInfo PublishSettingsFile
        {
            get;
            private set;
        }

        public bool ControllerInitialized;



        public virtual ObservableCollection<AzureSubscription> AzureSubscriptions
        {
            get;
            set;
        }

        #endregion

        public async Task StartAllAsync(AzureSubscription subscription)
        {
            await Task.Factory.StartNew(() =>
            {

                Logger.LogEntry(LogType.Info, "Starting all VMs");

                //barrier used to disabled UI until we are working
                Barrier stopWorkingBarrier = new Barrier(subscription.VMs.Count, null);
                IsWorking = true;
                Parallel.ForEach<AzureVM>(subscription.VMs, vm =>
                {
                    vm.StartAsync().Wait();
                    stopWorkingBarrier.SignalAndWait();
                });
                IsWorking = false;
                Logger.LogEntry(LogType.Info, "VMs started successfully");

            });
        }


        public async void StopAllAsync(AzureSubscription subscription)
        {

            await Task.Factory.StartNew(() =>
            {

                Logger.LogEntry(LogType.Info, "Stopping all VMs");

                //barrier used to disabled UI until we are working
                Barrier stopWorkingBarrier = new Barrier(subscription.VMs.Count, null);
                IsWorking = true;
                Parallel.ForEach<AzureVM>(subscription.VMs, vm =>
               {
                   vm.StopAsync().Wait();
                   stopWorkingBarrier.SignalAndWait();
               });
                IsWorking = false;
                Logger.LogEntry(LogType.Info, "VMs stopped successfully");

            });

        }




        public async Task StopVMAsync(AzureVM vm)
        {
            if (!ControllerInitialized)
            {
                Logger.LogEntry(LogType.Warning, "Controller not ready");
                return;
            }

            Logger.LogEntry(LogType.Info, "Stopping " + vm.Name);

#if OFFLINE
                        await Task.Delay(5000);
                        vm.Status = VMStatus.Off;
#else

            try
            {

                IsWorking = true;

                vm.Status = VMStatus.Updating;

                ComputeManagementClient client = new ComputeManagementClient(vm.Subscription.CloudCredentials);
                await client.VirtualMachines.ShutdownAsync(vm.ServiceName, vm.DeploymentName, vm.Name, new VirtualMachineShutdownParameters());
                vm.Status = VMStatus.Off;

            }
            catch (Exception pse)
            {
                vm.Status = VMStatus.Error;
                Logger.LogEntry("Could not stop " + vm.Name, pse);
                throw new Exception("Stop failed");
            }
            finally
            {
                IsWorking = false;
            }

#endif
        }

        public async Task StartVMAsync(AzureVM vm)
        {
            if (!ControllerInitialized)
            {
                Logger.LogEntry(LogType.Warning, "Controller not ready");
                return;
            }

            Logger.LogEntry(LogType.Info, "Starting " + vm.Name);

#if OFFLINE
                    await Task.Delay(5000);
                    vm.Status = VMStatus.Running;
                    return;
#else

            try
            {

                IsWorking = true;

                vm.Status = VMStatus.Updating;

                ComputeManagementClient client = new ComputeManagementClient(vm.Subscription.CloudCredentials);
                await client.VirtualMachines.StartAsync(vm.ServiceName, vm.DeploymentName, vm.Name);
                vm.Status = VMStatus.Running;
            }
            catch (Exception pse)
            {
                vm.Status = VMStatus.Error;
                Logger.LogEntry("Could not start " + vm.Name, pse);
                throw new Exception("Start failed");
            }
            finally
            {
                IsWorking = false;
            }

#endif
        }




        public async Task RetrieveVMsAsync(AzureSubscription subscription)
        {
            if (!ControllerInitialized)
            {
                Logger.LogEntry(LogType.Warning, "Controller not ready");
                return;
            }

            if (subscription == null)
            {
                Logger.LogEntry(LogType.Warning, "No subscription selected");
                return;
            }

            Logger.LogEntry(LogType.Info, String.Format("Downloading VMs for \"{0}\"", subscription.Name));

#if OFFLINE
            await Task.Delay(5000);
            for (int i = 0; i < 10; i++)
            {
                AzureVM vm = new AzureVM(subscription);
                vm.Name = "VM " + i;
                vm.FQDN = "vm.cloudapp.net";
                vm.ServiceName = vm.Name + "Service";
                vm.Size = "Small";
                vm.LocalIPAddress = IPAddress.Parse("192.168.1.1");
                vm.RdpPort = 3389;

                vm.Status = VMStatus.Running;
                subscription.VMs.Add(vm);
            }
            //return;
#else


            await Task.Factory.StartNew(() =>
            {
                try
                {
                    IsWorking = true;
                    //1 command for all machines

                    ComputeManagementClient client = new ComputeManagementClient(subscription.CloudCredentials);

                    var hostedServices = client.HostedServices.List();
                    foreach (var service in hostedServices)
                    {
                        var deployment = GetAzureDeyployment(client, service.ServiceName, DeploymentSlot.Production);
                        if (deployment != null)
                        {
                            if (deployment.Roles.Count > 0)
                            {
                                foreach (var role in deployment.Roles)
                                {
                                    if (role.RoleType == VirtualMachineRoleType.PersistentVMRole.ToString())
                                    {
                                        //find the instance related to this VM, it does contains more information
                                        var realInstance = deployment.RoleInstances.Where((i) => i.RoleName == role.RoleName).FirstOrDefault();

                                        AzureVM vm = new AzureVM(subscription);
                                        vm.Name = role.RoleName;
                                        vm.ServiceName = service.ServiceName;
                                        vm.Size = role.RoleSize;
                                        vm.DeploymentName = deployment.Name;
                                        vm.FQDN = deployment.Uri.DnsSafeHost;

                                        if(!String.IsNullOrEmpty(realInstance.IPAddress))
                                            vm.LocalIPAddress = IPAddress.Parse(realInstance.IPAddress);

                                        switch (realInstance.PowerState)
                                        {
                                            case RoleInstancePowerState.Stopped: { vm.Status = VMStatus.Off; break; }
                                            case RoleInstancePowerState.Started: { vm.Status = VMStatus.Running; break; }
                                            default: { vm.Status = VMStatus.Updating; break; }
                                        }


                                        //search for any RDP endpoint
                                        if (role.ConfigurationSets != null && role.ConfigurationSets.Count > 0)
                                        {
                                            foreach (ConfigurationSet cs in role.ConfigurationSets)
                                            {
                                                if (cs.ConfigurationSetType == ConfigurationSetTypes.NetworkConfiguration.ToString())
                                                {
                                                    //dynamic ncs = cs ;
                                                    foreach (InputEndpoint ie in cs.InputEndpoints)
                                                    {
                                                        if (ie.Name == "Remote Desktop")
                                                        {
                                                            vm.RdpPort = ie.Port.Value;
                                                            Logger.LogEntry(LogType.Verbose, "Found RDP endpoint for " + vm.Name);
                                                        }
                                                    }
                                                }
                                            }
                                        }

                                        Logger.LogEntry(LogType.Info, String.Format("Found VM: {0} ({1})", vm.Name, vm.ServiceName));


                                        if (!subscription.VMs.Contains(vm))
                                        {
                                            Application.Current.Dispatcher.Invoke(() => subscription.VMs.Add(vm)); //because VMs are bound to UI                                            
                                        }
                                    }

                                }
                            }
                        }
                    }


                    Logger.LogEntry(LogType.Info, "Refresh completed");
                }
                catch (Exception pse)
                {
                    Logger.LogEntry("Could not retrieve VMs", pse);
                }
                finally
                {
                    IsWorking = false;
                }
            }
            );
#endif

        }

        private static DeploymentGetResponse GetAzureDeyployment(ComputeManagementClient client, string serviceName, DeploymentSlot slot)
        {
            try
            {
                return client.Deployments.GetBySlot(serviceName, slot);

            }
            catch (CloudException ex)
            {

                if (ex.ErrorCode == "ResourceNotFound")
                {
                    return null;
                }
                else
                {
                    throw ex;
                }
            }
        }      

        /// <summary>
        /// Check if Azure Powershell commands work, and discover all the subscriptions in the publishing settings file
        /// Then registers all the Subscriptions specified the file publishing settings file into the current user profile
        /// Each subscription will then be available for the user
        /// </summary>
        /// <param name="publishSettingsFile">File generated by Get-AzurePublishSettingsFile, contains all the subscirptions for the user and the management certificate</param>
        /// <returns>false in case of errors</returns>
        public bool InitializeController(FileInfo publishSettingsFile)
        {
            //STEPS
            // 1- Initialize each subscription

            bool result = false;
            Logger.LogEntry(LogType.Info, "Initializing Azure PS Controller");
            IsWorking = true;

            try
            {

                //if (!CheckIfAzurePSIsInstalled())
                //    return false;

                if (publishSettingsFile == null || !publishSettingsFile.Exists)
                    throw new Exception("Publish Settings file not valid");

                PublishSettingsFile = publishSettingsFile; //file exists!
                Logger.LogEntry(LogType.Verbose, "Publish Settings File: " + PublishSettingsFile.FullName);

                AzureSubscriptions.Clear();

                DiscoverSubscriptions(); //discover Azure Subscriptions and add them to the main collection

                Logger.LogEntry(LogType.Info, "Azure PS Controller Initialized!");
                result = true;
            }
            catch (Exception pse)
            {
                Logger.LogEntry("Could not initiliaze the Azure PS Controller", pse);
                result = false;
            }
            finally
            {
                ControllerInitialized = result;
                IsWorking = false;
            }

            return result;
        }

        /// <summary>
        /// Goes through the Publish settings file to find out all the Subscriptions, it adds the results to the Subscriptions collection
        /// </summary>
        private void DiscoverSubscriptions()
        {

            XmlDocument doc = new XmlDocument();
            doc.Load(PublishSettingsFile.FullName);

            var subNodes = doc.SelectNodes("//Subscription");
            foreach (XmlElement s in subNodes)
            {

                AzureSubscription newAS = new AzureSubscription(this, s.Attributes["Id"].Value, s.Attributes["ManagementCertificate"].Value, s.Attributes["Name"].Value);

                Logger.LogEntry(LogType.Info, String.Format("Found Azure Subscription: \"{0}\"", newAS.Name));
                if (!AzureSubscriptions.Contains(newAS))
                    AzureSubscriptions.Add(newAS);
            }
        }


        /// <summary>
        /// Execute the PS commands that create a file with all the Subscriptions and the management certificate
        /// This file is needed to execute the PS Azure commands
        /// </summary>
        public void DownloadPublishSettings()
        {
            try
            {
                Logger.LogEntry(LogType.Info, "Downloading Azure Publish Settings - Waiting for User");

                Process.Start("https://manage.windowsazure.com/publishsettings/index?client=powershell");
            }
            catch (Exception pse)
            {
                Logger.LogEntry("Could not Download Azure publish settings", pse);
            }
        }
    }
}

